
package cn.iocoder.yudao.module.apijson.framework;

import apijson.RequestMethod;
import apijson.StringUtil;
import apijson.orm.AbstractSQLConfig;
import apijson.orm.AbstractSQLExecutor;

import java.util.*;
import java.util.Map.Entry;


/**表字段相关工具类
 * @author Lemon
 * @see 先提前配置 {@link #VERSIONED_TABLE_COLUMN_MAP}, {@link #VERSIONED_KEY_COLUMN_MAP} 等，然后调用相关方法。
 * 不支持直接关联 database, schema, datasource，可以把这些与 table 拼接为一个字符串传给参数 table，格式可以是 database-schema-datasource-table
 */
public class ColumnUtil {

	/**带版本的表和字段一对多对应关系，用来做 反选字段
	 * Map<version, Map<table, [column0, column1...]>>
	 */
	public static SortedMap<Integer, Map<String, List<String>>> VERSIONED_TABLE_COLUMN_MAP;

	/**带版本的 JSON key 和表字段一对一对应关系，用来做字段名映射
	 * Map<version, Map<table, Map<key, column>>>
	 */
	public static SortedMap<Integer, Map<String, Map<String, String>>> VERSIONED_KEY_COLUMN_MAP;

	/**带版本的 JSON key 和表字段一对一对应关系，用来做字段名映射，与 VERSIONED_KEY_COLUMN_MAP 相反
	 * Map<version, Map<table, Map<column, key>>>
	 */
	private static SortedMap<Integer, Map<String, Map<String, String>>> VERSIONED_COLUMN_KEY_MAP;

	public static final Comparator<Integer> DESC_COMPARATOR = new Comparator<Integer>() {
		@Override
		public int compare(Integer o1, Integer o2) {
			if (o2 == null) {
				return o1 == null ? 0 : -1;
			}
			return o2.compareTo(o1);
		}
	};

	static {
		VERSIONED_TABLE_COLUMN_MAP = new TreeMap<>(DESC_COMPARATOR);
		VERSIONED_KEY_COLUMN_MAP = new TreeMap<>(DESC_COMPARATOR);
		VERSIONED_COLUMN_KEY_MAP = new TreeMap<>(DESC_COMPARATOR);
	}

	/**初始化
	 */
	public static void init() {
		VERSIONED_COLUMN_KEY_MAP.clear();

		// 反过来补全 column -> key 的配置，以空间换时间
		Set<Entry<Integer, Map<String, Map<String, String>>>> set = VERSIONED_KEY_COLUMN_MAP.entrySet();
		if (!set.isEmpty()) {

			SortedMap<Integer, Map<String, Map<String, String>>> map = new TreeMap<>(DESC_COMPARATOR);

			for (Entry<Integer, Map<String, Map<String, String>>> entry : set) {

				Map<String, Map<String, String>> tableKeyColumnMap = entry == null ? null : entry.getValue();
				Set<Entry<String, Map<String, String>>> tableKeyColumnSet = tableKeyColumnMap == null ? null : tableKeyColumnMap.entrySet();

				if (tableKeyColumnSet != null && !tableKeyColumnSet.isEmpty()) {

					Map<String, Map<String, String>> tableColumnKeyMap = new HashMap<>();

					for (Entry<String, Map<String, String>> tableKeyColumnEntry : tableKeyColumnSet) {

						Map<String, String> keyColumnMap = tableKeyColumnEntry == null ? null : tableKeyColumnEntry.getValue();
						Set<Entry<String, String>> keyColumnSet = keyColumnMap == null ? null : keyColumnMap.entrySet();

						if (keyColumnSet != null && !keyColumnSet.isEmpty()) {
							Map<String, String> columnKeyMap = new HashMap<>();
							for (Entry<String, String> keyColumnEntry : keyColumnSet) {
								if (keyColumnEntry == null) {
									continue;
								}

								columnKeyMap.put(keyColumnEntry.getValue(), keyColumnEntry.getKey());
							}

							tableColumnKeyMap.put(tableKeyColumnEntry.getKey(), columnKeyMap);
						}
					}

					map.put(entry.getKey(), tableColumnKeyMap);
				}
			}

			VERSIONED_COLUMN_KEY_MAP = map;
		}


		// 补全剩下未定义别名的 key，以空间换时间
		Set<Entry<Integer, Map<String, List<String>>>> allSet = VERSIONED_TABLE_COLUMN_MAP.entrySet();
		if (!allSet.isEmpty()) {
			
			for (Entry<Integer, Map<String, List<String>>> entry : allSet) {
                Map<String, Map<String, String>> keyColumnMap = VERSIONED_KEY_COLUMN_MAP.computeIfAbsent(entry.getKey(), k -> new LinkedHashMap<>());
// 没必要，没特殊配置的就原样返回，没有安全隐患，还能减少性能浪费				Map<String, Map<String, String>> columnKeyMap = VERSIONED_COLUMN_KEY_MAP.get(entry.getKey());
                //				if (columnKeyMap == null) {
//					columnKeyMap = new LinkedHashMap<>();
//					VERSIONED_COLUMN_KEY_MAP.put(entry.getKey(), columnKeyMap);
//				}
				
				Map<String, List<String>> tableKeyColumnMap = entry.getValue();
				Set<Entry<String, List<String>>> tableKeyColumnSet = tableKeyColumnMap == null ? null : tableKeyColumnMap.entrySet();

				if (tableKeyColumnSet != null && !tableKeyColumnSet.isEmpty()) {

					for (Entry<String, List<String>> tableKeyColumnEntry : tableKeyColumnSet) {

						List<String> list = tableKeyColumnEntry == null ? null : tableKeyColumnEntry.getValue();

						if (list != null && !list.isEmpty()) {

                            Map<String, String> kcm = keyColumnMap.computeIfAbsent(tableKeyColumnEntry.getKey(), k -> new LinkedHashMap<>());
//							Map<String, String> ckm = columnKeyMap.get(tableKeyColumnEntry.getKey());
                            //							if (ckm == null) {
//								ckm = new LinkedHashMap<>();
//								columnKeyMap.put(tableKeyColumnEntry.getKey(), ckm);
//							}

							for (String column : list) {
								if (column == null) {
									continue;
								}

//								ckm.putIfAbsent(column, column);
								//FIXME 对 Comment.toId (多版本) 居然不起作用
//								if (kcm.containsValue(column) == false) {
									kcm.putIfAbsent(column, column);
//								}
							}

//							for (String column : list) {
//								if (column == null || ckm.get(column) != null) {
//									continue;
//								}
//
//								kcm.putIfAbsent(column, column);
//							}

						}
					}

				}
			}

		}

	}

	/**适配请求参数 JSON 中 @column:value 的 value 中的 key。支持 !key 反选字段 和 字段名映射

	 */
	public static List<String> compatInputColumn(List<String> columns, String table, RequestMethod method) {
		return compatInputColumn(columns, table, method, null, false);
	}

	/**适配请求参数 JSON 中 @column:value 的 value 中的 key。支持 !key 反选字段 和 字段名映射
	 * @see 先提前配置 {@link #VERSIONED_TABLE_COLUMN_MAP}，然后在 {@link AbstractSQLConfig} 的子类重写 {@link AbstractSQLConfig#setColumn } 并调用这个方法，例如
	 * <pre >
	public AbstractSQLConfig<T, M, L> setColumn(List<String> column) { <br>
	return super.setColumn(ColumnUtil.compatInputColumn(column, getTable(), version)); <br>
	}
	 * </pre>
	 */
	public static List<String> compatInputColumn(List<String> columns, String table, RequestMethod method, Integer version, boolean throwWhenNoKey) {
		String[] keys = columns == null ? null : columns.toArray(new String[]{});  // StringUtil.split(c, ";");
		if (keys == null || keys.length <= 0) { // JOIN 副表可以设置 @column:"" 来指定不返回字段
			return columns != null ? columns : getClosestValue(VERSIONED_TABLE_COLUMN_MAP, version, table);
		}

		//		boolean isQueryMethod = RequestMethod.isQueryMethod(method);

		List<String> exceptColumns = new ArrayList<>(); // Map<String, String> exceptColumnMap = new HashMap<>();
		List<String> newColumns = new ArrayList<>();

		Map<String, String> keyColumnMap = getClosestValue(VERSIONED_KEY_COLUMN_MAP, version, table);
		boolean isEmpty = keyColumnMap == null || keyColumnMap.isEmpty();

		String q = "`";

		String expression;
		//...;fun0(arg0,arg1,...):fun0;fun1(arg0,arg1,...):fun1;...
        for (String key : keys) {

            //!column,column2,!column3,column4:alias4;fun(arg0,arg1,...)
            expression = key;
            int start = expression.indexOf("(");
            int end = expression.lastIndexOf(")");
            if (start >= 0 && start < end) {
                String[] ks = StringUtil.split(expression.substring(start + 1, end));

                StringBuilder expr = new StringBuilder(expression.substring(0, start + 1));
                for (int j = 0; j < ks.length; j++) {
                    String ck = ks[j];
                    boolean hasQuote = false;
                    if (ck.endsWith("`")) {
                        String nck = ck.substring(0, ck.length() - 1);
                        if (nck.lastIndexOf("`") == 0) {
                            ck = nck.substring(1);
                            hasQuote = true;
                        }
                    }

                    String rc = null;
                    if (hasQuote || StringUtil.isName(ck)) {
                        rc = isEmpty ? null : keyColumnMap.get(ck);
                        if (rc == null && !isEmpty && throwWhenNoKey) {
                            throw new NullPointerException(table + ":{ @column: value } 的 value 中 " + ck + " 不合法！不允许传后端未授权访问的字段名！");
                        }
                    }

                    expr.append(j == 0 ? "" : ",").append(hasQuote ? q : "").append(rc == null ? ck : rc).append(hasQuote ? q : "");
                }

                newColumns.add(expr + expression.substring(end));

//				newColumns.add(expression);
                continue;
            }

            String[] ckeys = StringUtil.split(expression);
            if (ckeys != null) {
                for (String ckey : ckeys) {
                    String ck = ckey;

                    if (ck.startsWith("!")) {
                        if (ck.length() <= 1) {
                            throw new IllegalArgumentException("@column:value 的 value 中 " + ck
                                    + " 不合法！ !column 不允许 column 为空字符串！column,!column2,!column3,column4:alias4 中所有 column 必须符合变量名格式！");
                        }
                        String c = ck.substring(1);
                        if (!StringUtil.isName(c)) {
                            throw new IllegalArgumentException("@column:value 的 value 中 " + c
                                    + " 不合法！ column,!column2,!column3,column4:alias4 中所有 column 必须符合变量名格式！");
                        }

                        String rc = isEmpty ? null : keyColumnMap.get(c);
                        exceptColumns.add(rc == null ? c : rc);  // 不使用数据库别名，以免 JOIN 等复杂查询情况下报错字段不存在	exceptColumnMap.put(nc == null ? c : nc, c);  // column:alias
                    } else {
                        boolean hasQuote = false;
                        if (ck.endsWith("`")) {
                            String nck = ck.substring(0, ck.length() - 1);
                            if (nck.lastIndexOf("`") == 0) {
                                ck = nck.substring(1);
                                hasQuote = true;
                            }
                        }

                        String rc = null;
                        if (hasQuote || StringUtil.isName(ck)) {
                            rc = isEmpty ? null : keyColumnMap.get(ck);
                            if (rc == null && !isEmpty && throwWhenNoKey) {
                                throw new NullPointerException(table + ":{ @column: value } 的 value 中 " + ck + " 不合法！不允许传后端未授权访问的字段名！");
                            }
                        }

                        newColumns.add(rc == null ? ck : rc);  // 不使用数据库别名，以免 JOIN 等复杂查询情况下报错字段不存在 newColumns.add(rc == null ? ck : (isQueryMethod ? (rc + ":" + ck) : rc));
                    }
                }
            }
        }

		List<String> allColumns = exceptColumns.isEmpty() ? null : getClosestValue(VERSIONED_TABLE_COLUMN_MAP, version, table);

		if (allColumns != null && !allColumns.isEmpty()) {

			// 不使用数据库别名，以免 JOIN 等复杂查询情况下报错字段不存在
			//			Map<String, Map<String, String>> tableColumnKeyMap = VERSIONED_COLUMN_KEY_MAP == null || VERSIONED_COLUMN_KEY_MAP.isEmpty() ? null : VERSIONED_COLUMN_KEY_MAP.get(version);
			//			Map<String, String> columnKeyMap = tableColumnKeyMap == null || tableColumnKeyMap.isEmpty() ? null : tableColumnKeyMap.get(table);

			for (String c : allColumns) {
				if (c != null && !exceptColumns.contains(c)) {  // column:alias
					// 不使用数据库别名，以免 JOIN 等复杂查询情况下报错字段不存在		String alias = isQueryMethod == false || columnKeyMap == null || columnKeyMap.isEmpty() ? null : columnKeyMap.get(c);
					newColumns.add(c); // newColumns.add(alias == null ? c : (c + ":" + alias));
				}
			}
		}

		return newColumns;
	}


	/**适配请求参数 JSON 中 条件/赋值 键值对的 key
	 */
	public static String compatInputKey(String key, String table, RequestMethod method) {
		return compatInputKey(key, table, method, null, false);
	}

	/**适配请求参数 JSON 中 条件/赋值 键值对的 key
	 * @see 先提前配置 {@link #VERSIONED_KEY_COLUMN_MAP}，然后在 {@link AbstractSQLConfig} 的子类重写 {@link AbstractSQLConfig#getKey } 并调用这个方法，例如
	 * <pre >
	public String getKey(String key) { <br>
	return super.getKey(ColumnUtil.compatInputKey(key, getTable(), version)); <br>
	}
	 * </pre>
	 */
	public static String compatInputKey(String key, String table, RequestMethod method, Integer version, boolean throwWhenNoKey) {
		Map<String, String> keyColumnMap = getClosestValue(VERSIONED_KEY_COLUMN_MAP, version, table);
		boolean isEmpty = keyColumnMap == null || keyColumnMap.isEmpty();
		String alias = isEmpty ? null : keyColumnMap.get(key);
		if (alias == null) {
			if (!isEmpty && throwWhenNoKey) {
				throw new NullPointerException(table + ":{} 中不允许传 " + key + " ！");
			}
			return key;
		}

		return alias;
	}

	/**适配返回结果 JSON 中键值对的 key。可能通过不传 @column 等方式来返回原始字段名，这样就达不到隐藏真实字段名的需求了，所以只有最终这个兜底方式靠谱。
	 */
	public static String compatOutputKey(String key, String table, RequestMethod method) {
		return compatOutputKey(key, table, method, null);
	}

	/**适配返回结果 JSON 中键值对的 key。可能通过不传 @column 等方式来返回原始字段名，这样就达不到隐藏真实字段名的需求了，所以只有最终这个兜底方式靠谱。
	 * @see 先提前配置 {@link #VERSIONED_COLUMN_KEY_MAP}，然后在 {@link AbstractSQLExecutor} 的子类重写 {@link AbstractSQLExecutor#getKey } 并调用这个方法，例如
	 * <pre >
	protected String getKey(SQLConfig<T, M, L> config, ResultSet rs, ResultSetMetaData rsmd, int tablePosition, JSONMap table,
	int columnIndex, Map<String, JSONMap> childMap) throws Exception { <br>
	return ColumnUtil.compatOutputKey(super.getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap), config.getTable(), config.getMethod(), version); <br>
	}
	 * </pre>
	 */
	public static String compatOutputKey(String key, String table, RequestMethod method, Integer version) {
		Map<String, String> columnKeyMap = getClosestValue(VERSIONED_COLUMN_KEY_MAP, version, table);
		String alias = columnKeyMap == null || columnKeyMap.isEmpty() ? null : columnKeyMap.get(key);
		return alias == null ? key : alias;
	}

	public static <T> T getClosestValue(SortedMap<Integer, Map<String, T>> versionedMap, Integer version, String table) {
		boolean isEmpty = versionedMap == null || versionedMap.isEmpty();

		Map<String, T> map = isEmpty || version == null ? null : versionedMap.get(version);
		T m = map == null ? null : map.get(table);
		if (!isEmpty && m == null) {
			Set<Entry<Integer, Map<String, T>>> set = versionedMap.entrySet();

			T lm = null;
			for (Entry<Integer, Map<String, T>> entry : set) {
				Map<String, T> val = entry.getValue();
				m = val == null ? null : val.get(table);
				if (m == null) {
					continue;
				}

				if (version == null || version == 0) {
					//	versionedMap.put(null, val);
					return m;
				}

				Integer key = entry.getKey();
				if (key == null) {
					lm = m;
					map = val;
					continue;
				}

				if (version >= key) {
					versionedMap.put(version, val);
					return m;
				}

				break;
			}

			if (lm != null) {
				m = lm;
			}

			if (map != null) {
				versionedMap.put(version, map);
			}
		}

		return m;
	}


	/**把多个表名相关属性拼接成一个表名
	 */
	public static String concat(String database, String schema, String datasource, String table) {
		return database + "-" + schema + "-" + datasource + "-" + table;
	}


}
